package org.xx.armory.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.xx.armory.Application;
import org.xx.armory.commons.LifeCycle;

import java.net.UnknownHostException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static io.netty.channel.ChannelOption.SO_BACKLOG;
import static io.netty.channel.ChannelOption.SO_KEEPALIVE;
import static java.net.InetAddress.getLocalHost;
import static java.net.InetAddress.getLoopbackAddress;
import static org.apache.commons.lang3.StringUtils.endsWithIgnoreCase;
import static org.apache.commons.lang3.StringUtils.substring;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.apache.commons.lang3.math.NumberUtils.toInt;
import static org.xx.armory.commons.SysUtils.format;

/**
 * 使用Netty提供HTTP协议服务的应用程序。
 *
 * @author Haart
 */
public class NettyApplication
        extends Application
        implements LifeCycle {
    protected static final int K_BYTES = 1024;
    protected static final int M_BYTES = 1024 * K_BYTES;
    protected static final String DEFAULT_HOST = "0.0.0.0";
    protected static final int DEFAULT_PORT = 8080;
    protected static final int DEFAULT_MAX_CONTENT_LENGTH = 20 * M_BYTES;

    private final Object activeLock;

    private volatile String host;
    private volatile int port;
    private volatile int maxContentLength;
    private volatile Channel channel;
    private volatile EventLoopGroup bossGroup;
    private volatile EventLoopGroup workerGroup;
    private volatile ClosableChannelInboundHandler coreHandler;

    /**
     * 构造{@code NettyApplication}实例。
     */
    protected NettyApplication() {
        this.activeLock = new Object();
        this.host = DEFAULT_HOST;
        this.port = DEFAULT_PORT;
        this.maxContentLength = DEFAULT_MAX_CONTENT_LENGTH;
        this.bossGroup = null;
        this.workerGroup = null;
        this.coreHandler = null;
    }

    /**
     * 获取本机IP地址。
     *
     * @return 本机IP地址。
     * @see java.net.InetAddress#getLocalHost()
     * @see java.net.InetAddress#getLoopbackAddress()
     */
    private static String getHostAddress() {
        try {
            return getLocalHost().getHostAddress();
        } catch (UnknownHostException ex) {
            return getLoopbackAddress().getHostAddress();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final synchronized void start()
            throws Exception {
        if (this.channel != null) {
            throw new IllegalStateException("channel is not null!");
        }

        super.start();

        this.bossGroup = new NioEventLoopGroup(1);
        this.workerGroup = new NioEventLoopGroup();
        this.channel = null;

        final Date tick1 = new Date();

        final ServerBootstrap b = new ServerBootstrap()
                .group(this.bossGroup, this.workerGroup)
                .channel(NioServerSocketChannel.class);

        final ClosableChannelInboundHandler coreHandler = createCoreHandler();
        final ChannelInboundHandler nfHandler = createNotFoundHandler();
        this.coreHandler = coreHandler;

        final ChannelFuture cf = b
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    /**
                     * {@inheritDoc}
                     */
                    @Override
                    protected void initChannel(final SocketChannel ch)
                            throws Exception {
                        // 通过配置文件读取超时参数。
                        // 以下Handler不是Sharable，所以必须单独创建。
                        final ChannelInboundHandler readTimeoutHandler = new ReadTimeoutHandler(10, TimeUnit.SECONDS);
                        final ChannelOutboundHandler writeTimeoutHandler = new WriteTimeoutHandler(30, TimeUnit.SECONDS);

                        ch.pipeline()
                          .addLast(readTimeoutHandler)
                          .addLast(writeTimeoutHandler)
                          .addLast(new HttpRequestDecoder())
                          .addLast(new HttpResponseEncoder())
                          // 设置请求和响应的缓冲区
                          .addLast(new HttpObjectAggregator(getMaxContentLength(), true))
                          .addLast(coreHandler)
                          .addLast(nfHandler);
                    }
                })
                .option(SO_BACKLOG, 128)
                .childOption(SO_KEEPALIVE, true)
                .bind(getHost(), getPort()).sync();

        final Date tick2 = new Date();
        log(LOG_INFO, format("server started after {} ms", tick2.getTime() - tick1.getTime()));

        this.channel = cf.channel();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final synchronized void shutdown()
            throws Exception {
        if (this.channel != null || this.coreHandler != null) {
            try {
                if (this.channel != null && this.channel.isActive()) {
                    this.channel.close().sync();
                }
                this.channel = null;

                if (this.coreHandler != null) {
                    this.coreHandler.close();
                }
                this.coreHandler = null;

                log(LOG_INFO, "server shutdown");
            } catch (InterruptedException ex) {
                log(LOG_WARN, "interrupted while shutting down server", ex);
            } finally {
                this.bossGroup.shutdownGracefully();
                this.workerGroup.shutdownGracefully();
            }
        }

        super.shutdown();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean parseArguments(
            List<String> args
    ) {
        if (!super.parseArguments(args)) {
            return false;
        }

        final Options opts = new Options()
                .addOption("h", "host", true, "host of listener")
                .addOption("p", "port", true, "port of listener")
                .addOption("", "mcl", true, "max content length of request");

        try {
            final CommandLine commandLine = new DefaultParser().parse(opts, args.toArray(new String[0]), null, true);

            final String host = commandLine.getOptionValue("h", "").trim().toLowerCase();
            final int port = toInt(commandLine.getOptionValue("port", ""));
            final String mcl = commandLine.getOptionValue("mcl", "").trim().toUpperCase();
            final int mc;
            if (endsWithIgnoreCase(mcl, "M")) {
                // max-content参数必须以M结尾，表示MB
                mc = toInt(substring(mcl, 0, -1), 0);
            } else {
                mc = 0;
            }

            setHost(host);
            setPort(port);
            setMaxContentLength(mc);

            args.clear();
            args.addAll(commandLine.getArgList());

            return true;
        } catch (ParseException | IllegalArgumentException ex) {
            new HelpFormatter().printHelp(getName() + " [options] ...", "", opts, "", true);
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initialize()
            throws Exception {
        synchronized (this.activeLock) {
            super.initialize();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void dispose()
            throws Exception {
        synchronized (this.activeLock) {
            super.dispose();

            this.activeLock.notifyAll();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void executeMain()
            throws Exception {
        super.executeMain();
        start();
        waitForEver();
    }

    /**
     * 等待，直到应用程序被清理，不再可用。
     */
    private void waitForEver() {
        synchronized (this.activeLock) {
            while (isActive()) {
                try {
                    this.activeLock.wait();
                } catch (InterruptedException ignored) {
                    break;
                }
            }
        }
    }

    /**
     * 获取主机地址。
     *
     * @return 主机地址。
     */
    public final String getHost() {
        return this.host;
    }

    /**
     * 设置主机地址。
     *
     * @param host
     *         主机地址。
     */
    public final void setHost(
            String host
    ) {
        host = trimToEmpty(host);
        this.host = !host.isEmpty() ? host : DEFAULT_HOST;
    }

    /**
     * 获取端口号。
     *
     * @return 端口号。
     */
    public final int getPort() {
        return this.port;
    }

    /**
     * 设置端口号。
     * <p>如果参数{@code port}小于等于0则使用默认端口号。</p>
     *
     * @param port
     *         端口号。
     * @see #DEFAULT_PORT
     */
    public final void setPort(
            int port
    ) {
        this.port = port > 0 ? port : DEFAULT_PORT;
    }

    /**
     * 获取最大内容长度。
     *
     * @return 最大内容长度。
     */
    public final int getMaxContentLength() {
        return this.maxContentLength;
    }

    /**
     * 设置最大内容长度。
     *
     * @param maxContentLength
     *         最大内容长度。
     */
    public final void setMaxContentLength(
            int maxContentLength
    ) {
        this.maxContentLength = maxContentLength > 0 ? maxContentLength * M_BYTES : DEFAULT_MAX_CONTENT_LENGTH;
    }

    /**
     * 创建并初始化核心请求处理器。
     *
     * @return 核心请求处理器。
     * @throws Exception
     *         如果初始化核心请求处理器时出现错误。
     */
    protected ClosableChannelInboundHandler createCoreHandler()
            throws Exception {
        return new CoreHandler(getConfigurationUrl(null));
    }

    /**
     * 创建输出{@literal 404 NotFound}的请求处理器。
     *
     * @return 请求处理器。
     * @throws Exception
     *         如果初始化请求处理器时出现错误。
     */
    protected ChannelInboundHandler createNotFoundHandler()
            throws Exception {
        return new NotFoundHandler();
    }
}
